參數驗證 middleware 可以用於 Actions 及事件的參數驗證。
Moleculer 預設使用與本框架同一個作者所開發的 fastest-validator 套件[2] 。
範例:快速使用,可以設為 "Fastest" 或 true 。
moleculer.config.js
module.exports = {
    nodeID: "node-100",
    validator: "Fastest"
}
範例:選項設定,更多選項請參閱[2]
moleculer.config.js
module.exports = {
    nodeID: "node-100",
    validator: {
        type: "Fastest",
        options: {
            useNewCustomCheckerFunction: true,
            defaults: { /*...*/ },
            messages: { /*...*/ },
            aliases: { /*...*/ }
        }
    }
}
要執行參數驗證之前,需要先在 action 定義好需要的參數名稱,然後再建立參數的驗證綱目。
範例:
const { ServiceBroker } = require("moleculer");
const broker = new ServiceBroker({
    validator: true // 預設為啟用
});
broker.createService({
    name: "say",
    actions: {
        hello: {
            // 驗證器綱目
            params: {
                name: { type: "string", min: 2 }
            },
            handler(ctx) {
                return "Hello " + ctx.params.name;
            }
        }
    }
});
broker.call("say.hello").then(console.log)
    .catch(err => console.error(err.message));
// -> throw ValidationError: "The 'name' field is required!"
broker.call("say.hello", { name: 123 }).then(console.log)
    .catch(err => console.error(err.message));
// -> throw ValidationError: "The 'name' field must be a string!"
broker.call("say.hello", { name: "Walter" }).then(console.log)
    .catch(err => console.error(err.message));
範例:驗證綱目
{
    id: { type: "number", positive: true, integer: true },
    name: { type: "string", min: 3, max: 255 },
    status: "boolean" // 簡寫定義,更多請參閱[3]
}
Fastest Validator 在 v1.11.0 版後支援了異步客製化驗證器,只要在 moleculer.config.js 設定 useNewCustomCheckerFunction: true 來啟動這個功能,就可以利用它來建立客製化驗證函數。在 Moleculer 中,客製化驗證器函數可以使用 ctx 資訊,它會被放在 context.meta 中,你可以透過 ctx 來使用 context 、 service 、 broker 資源,因此你可以在驗證器中執行異步呼叫 (例如: ctx.call )。
範例:啟用異步客製化驗證器
moleculer.config.js
module.exports = {
    validator: {
        type: "FastestValidator",
        options: {
            useNewCustomCheckerFunction: true,
            defaults: { /*...*/ },
            messages: { /*...*/ },
            aliases: { /*...*/ }
        }
    }
}
範例:使用異步客製化驗證器
posts.service.js
module.exports = {
    name: "posts",
    actions: {
        params: {
            $$async: true,
            owner: {
                type: "string",
                custom: async (value, errors, schema, name, parent, context) => {
                    // 由 meta 取得 ctx
                    const ctx = context.meta;
                    // 呼叫其它服務
                    const res = await ctx.call("users.isValid", {
                        id: value
                    });
                    if (res !== true)
                        errors.push({
                            type: "invalidOwner",
                            field: "owner",
                            actual: value
                        });
                    return value;
                }
            },
        },
        handler(ctx) {
            // ...
        }
    }
};
事件也可以使用參數驗證,它的參數驗證綱目設定方式與 Actions 類似。
注意,驗證錯誤時並不會像 Actions 一樣返回給發送來源,它只會將錯誤紀錄在 log 或是在全域錯誤處理被捕獲。
mailer.service.js
module.exports = {
    name: "mailer",
    events: {
        "send.mail": {
            params: {
                from: "string|optional",
                to: "email",
                subject: "string"
            },
            handler(ctx) {
                this.logger.info("Event received, parameters OK!", ctx.params);
            }
        }
    }
};
你也可以建立客製化的驗證器,官方建議可以參考 Fastest Validator[4] 的原始碼來修改,再實作 compile 、 validate 方法。
範例:建立 Joi 驗證器
使用前請安裝 joi[5] 套件
npm install joi --save。
joi.validator.js
const BaseValidator = require("moleculer").Validators.Base;
const { ValidationError } = require("moleculer").Errors;
class JoiValidator extends BaseValidator {
    constructor() {
        super();
    }
    compile(schema) {
        return (params) => this.validate(params, schema);
    }
    validate(params, schema) {
        const res = schema.validate(params);
        if (res.error)
            throw new ValidationError(res.error.message, null, res.error.details);
        return true;
    }
}
module.exports = JoiValidator;
範例:使用 Joi 驗證器
greeter.service.js
const { ServiceBroker } = require("moleculer");
const Joi = require("joi");
const JoiValidator = require("./joi.validator");
let broker = new ServiceBroker({
    logger: true,
    validator: new JoiValidator // 使用 Joi 驗證器
});
// --- 測試 BROKER ---
broker.createService({
    name: "greeter",
    actions: {
        hello: {
            /*params: {
                name: { type: "string", min: 4 }
            },*/
            params: Joi.object().keys({
                name: Joi.string().min(4).max(30).required()
            }),
            handler(ctx) {
                return `Hello ${ctx.params.name}`;
            }
        }
    }
});
broker.start()
    .then(() => broker.call("greeter.hello").then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data))
    // -> "name" is required ...
    .then(() => broker.call("greeter.hello", { name: 100 }).then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data))
    // -> "name" must be a string ...
    .then(() => broker.call("greeter.hello", { name: "Joe" }).then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data))
    // -> "name" length must be at least 4 characters long ...
    .then(() => broker.call("greeter.hello", { name: "John" }).then(res => broker.logger.info(res)))
    .catch(err => broker.logger.error(err.message, err.data));
更多驗證器可以參考官方模組清單:
https://moleculer.services/modules.html#validation
[1] Parameter Validation, https://moleculer.services/docs/0.14/validating.html
[2] fastest-validator, https://github.com/icebob/fastest-validator
[3] fastest-validator shorthand-definitions, https://github.com/icebob/fastest-validator#shorthand-definitions
[4] Moleculer Fastest, https://github.com/moleculerjs/moleculer/blob/master/src/validators/fastest.js
[5] joi, https://joi.dev/